package ca.swank.hq.hard;

import ca.swank.hardquor.*;

import javax.swing.*;
import java.awt.event.*;
import java.awt.*;
import java.util.ArrayList;
import java.util.TreeSet;
import java.util.Iterator;

public class HardUI extends JFrame implements hardquorUserGame
{
  // the skin to use
  private HardquorSkin skin;
  private String skinFile = "resources/hard/orangeandanime.hardquorskin";

  // shadow state and mouse tracking
  private int mouseX;
  private int mouseY;
  private static final int SHADOW_NONE = 0;
  private static final int SHADOW_WALK = 1;
  private static final int SHADOW_WALL = 3;
  private static final int SHADOW_WALK_ILLEGAL = 4;
  private static final int SHADOW_WALL_ILLEGAL = 5;
  private int shadowState = SHADOW_NONE;
  private WalkMove shadowWalk;
  private WallMove shadowWall;

  // instance variables for tracking board and reporting
  private HardUIPanel uiPanel;
  private int playerNumber;
  private hardquorGameClient gameClient;
  private UIBoard board;
  private MouseHandler responder;

  public HardUI(hardquorGameClient gameClient, int playerNumber, String skinFile)
  { this.gameClient = gameClient;
    this.playerNumber = playerNumber;
    this.skinFile = skinFile;
    board = new UIBoard();

    // construct the skin from a zip or a folder
    //if(skinFile.endsWith(".zip")) skin = new HardquorZipSkin(skinFile, this);
    //else skin = new HardquorFolderSkin(skinFile, this);
    skin = new HardquorResourceSkin(skinFile, this);

    initWindow();
  }

  private void initWindow()
  { // make mouse handler and board panel; link them
    responder = new MouseHandler();
    uiPanel = new HardUIPanel();
    uiPanel.addMouseListener(responder);
    uiPanel.addMouseMotionListener(responder);

    // make a window and add panel
    addWindowListener(new HardUICloseHandler());
    setTitle(skin.PANEL_TITLE);
    setSize(skin.PANEL_WIDTH, skin.PANEL_HEIGHT);
    getContentPane().add(uiPanel);

    // show 'er off
    setVisible(true);
  }

  public void notifyError(int errorCode)
  { System.out.println("error # " + errorCode);
  }

  public void notifyMove(int[] source, int[] target)
  { board.makeMove(source, target);
    responder.calculateShadow();
    uiPanel.showBoard();
  }

  public void notifyMove(int[] a1, int[] a2, int[] b1, int[] b2)
  { board.makeMove(a1, a2, b1, b2);
    responder.calculateShadow();
    uiPanel.showBoard();
  }

  public void notifySpeak(String user, String comment)
  { System.out.println(user + ": " + comment);
  }

  public void notifyTurn()
  { if(responder.calculateShadow())
    { uiPanel.showBoard();
    }
  }

  public void notifyWinner(String winnerName)
  { System.out.println("winner " + winnerName);
  }

  public void notifyQuit(String user)
  { setVisible(false);
  }

  class HardUIPanel extends JPanel implements ActionListener
  {
    JTextField commentEnter;
    //JTextArea commentHistory;

    public HardUIPanel()
    {
    }

    public void actionPerformed(ActionEvent e)
    { gameClient.speak(commentEnter.getText());
      commentEnter.setText("");
    }

    public void showBoard()
    { repaint();
    }

    public void paintComponent(Graphics g)
    {
      super.paintComponent(g);

      // draw board
      g.drawImage(skin.IMAGE_BOARD, 0, 0, null);

      // draw players
      int[] playerOneLoc = board.getPlayerLocation(hardquorBoard.PLAYER_ONE);
      g.drawImage(skin.IMAGE_PLAYER_ONE, skin.PLAYER_LOC_COLS[playerOneLoc[0]],
        skin.PLAYER_LOC_ROWS[playerOneLoc[1]], null);
      int[] playerTwoLoc = board.getPlayerLocation(hardquorBoard.PLAYER_TWO);
      g.drawImage(skin.IMAGE_PLAYER_TWO, skin.PLAYER_LOC_COLS[playerTwoLoc[0]],
        skin.PLAYER_LOC_ROWS[playerTwoLoc[1]], null);

      // draw wall counts
      g.drawImage(skin.IMAGE_COUNT[board.getPlayerWalls(hardquorBoard.PLAYER_TWO)], skin.COUNT_PLAYER_ONE_X, skin.COUNT_PLAYER_ONE_Y, null);
      g.drawImage(skin.IMAGE_COUNT[board.getPlayerWalls(hardquorBoard.PLAYER_ONE)], skin.COUNT_PLAYER_TWO_X, skin.COUNT_PLAYER_TWO_Y, null);

      // draw walls
      g.setColor(skin.COLOUR_WALL);
      Iterator wallsPlaced = board.getWallsPlaced().iterator();
      while(wallsPlaced.hasNext())
      { WallMove wall = (WallMove)wallsPlaced.next();
        int[] bottomLeft = wall.getBottomLeft();
        if(wall.isHorizontal())
        { g.fillRect(skin.WALL_HORIZONTAL_X[bottomLeft[0]], skin.WALL_HORIZONTAL_Y[bottomLeft[1]],
            skin.WALL_HORIZONTAL_WIDTH, skin.WALL_HORIZONTAL_HEIGHT);
        } else
        { g.fillRect(skin.WALL_VERTICAL_X[bottomLeft[0]], skin.WALL_VERTICAL_Y[bottomLeft[1]],
            skin.WALL_VERTICAL_WIDTH, skin.WALL_VERTICAL_HEIGHT);
        }
      }

      // highlight legal moves
      if(playerNumber == board.getTurn())
      { Iterator legalMoves = board.getLegalMoves(board.getPlayerLocation(playerNumber)).iterator();
        while(legalMoves.hasNext())
        { int[] current = (int[])legalMoves.next();
          g.drawImage(skin.IMAGE_LEGAL_WALK, skin.PLAYER_LOC_COLS[current[0]],
            skin.PLAYER_LOC_ROWS[current[1]], null);
        }
      }

      // draw shadow
      if(shadowState == SHADOW_WALL) g.setColor(skin.COLOUR_SHADOW_WALL_LEGAL);
      else if(shadowState == SHADOW_WALL_ILLEGAL) g.setColor(skin.COLOUR_SHADOW_WALL_ILLEGAL);

      if(shadowState == SHADOW_WALL || shadowState == SHADOW_WALL_ILLEGAL)
      { int[] bottomLeft = shadowWall.getBottomLeft();
        if(shadowWall.isHorizontal())
        { g.fillRect(skin.WALL_HORIZONTAL_X[bottomLeft[0]], skin.WALL_HORIZONTAL_Y[bottomLeft[1]],
            skin.WALL_HORIZONTAL_WIDTH, skin.WALL_HORIZONTAL_HEIGHT);
        } else
        { g.fillRect(skin.WALL_VERTICAL_X[bottomLeft[0]], skin.WALL_VERTICAL_Y[bottomLeft[1]],
            skin.WALL_VERTICAL_WIDTH, skin.WALL_VERTICAL_HEIGHT);
        }
      } else if(shadowState == SHADOW_WALK)
      { int[] target = shadowWalk.getTarget();
        if(playerNumber == hardquorBoard.PLAYER_ONE)
          g.drawImage(skin.IMAGE_PLAYER_ONE_SHADOW, skin.PLAYER_LOC_COLS[target[0]],
            skin.PLAYER_LOC_ROWS[target[1]], null);
        else if(playerNumber == hardquorBoard.PLAYER_TWO)
          g.drawImage(skin.IMAGE_PLAYER_TWO_SHADOW, skin.PLAYER_LOC_COLS[target[0]],
            skin.PLAYER_LOC_ROWS[target[1]], null);
      }

    }
  }


  /**
   * responds to mouse motion by sending moves to the server or calculating board 'shadows'
   */
  class MouseHandler extends MouseAdapter implements MouseMotionListener
  {
    public MouseHandler()
    {
    }

    public void mouseDragged(MouseEvent e)
    { // do nothing
    }

    public void mouseMoved(MouseEvent e)
    { // if mouse moved out, ensure shadow is none and repaint if necessary
      if(e.getID() == MouseEvent.MOUSE_EXITED)
      { if(shadowState != SHADOW_NONE)
        { shadowState = SHADOW_NONE;
          uiPanel.showBoard();
        }
      // calculate new shadow and repaint
      } else
      { mouseX = e.getX();
        mouseY = e.getY();
        if(calculateShadow())
        { uiPanel.showBoard();
        }
      }

    }

    /**
     * returns true iff shadow has changed
     */
    public boolean calculateShadow()
    {
      int walk_row = -1;
      int walk_col = -1;

      if(board.getTurn() != playerNumber)
      { if(shadowState == SHADOW_NONE)
          return false;
        shadowState = SHADOW_NONE;
        return true;
      }

      for(int r = 0; r < skin.HOT_SPOT_ROWS[0].length; r++)
      { if(mouseY < skin.HOT_SPOT_ROWS[1][r] && mouseY > skin.HOT_SPOT_ROWS[0][r])
        { walk_row = r;
          break;
        }
      }

      for(int c = 0; c < skin.HOT_SPOT_COLS[0].length; c++)
      { if(mouseX < skin.HOT_SPOT_COLS[1][c] && mouseX > skin.HOT_SPOT_COLS[0][c])
        { walk_col = c;
          break;
        }
      }

      if(walk_row != -1 && walk_col != -1)
      { int[] playerLoc = board.getPlayerLocation(board.getTurn());
        WalkMove latestMotion = new WalkMove(board.getPlayerLocation(playerNumber), new int[] {walk_col, walk_row});

        // ensure change before repaint
        if(shadowState == SHADOW_WALK && latestMotion.equals(shadowWalk)) return false;
        else shadowWalk = latestMotion;

        if(board.isLegalMove(shadowWalk) == hardquorUserGame.ERROR_NONE)
        { shadowState = SHADOW_WALK;
        }

        return true;
      }

      int horizontal_wall_row = -1;
      int horizontal_wall_col = -1;

      for(int c = 0; c < skin.WALL_HORIZONTAL_X.length; c++)
      { if(mouseX > skin.WALL_HORIZONTAL_X[c] && mouseX < skin.WALL_HORIZONTAL_X[c] + skin.WALL_HORIZONTAL_WIDTH)
        { horizontal_wall_col = c;
          break;
        }
      }

      for(int r = 0; r < skin.WALL_HORIZONTAL_Y.length; r++)
      { if(mouseY > skin.WALL_HORIZONTAL_Y[r] - skin.HOT_SPOT_WALL_PADDING
          && mouseY < skin.WALL_HORIZONTAL_Y[r] + skin.WALL_HORIZONTAL_HEIGHT + skin.HOT_SPOT_WALL_PADDING)
        { horizontal_wall_row = r;
          break;
        }
      }

      if(horizontal_wall_row != -1 && horizontal_wall_col != -1)
      { int a1x = horizontal_wall_col;
        int a1y = horizontal_wall_row;

        WallMove latestMotion = new WallMove(new int[] {a1x, a1y}, new int[] {a1x+1, a1y},
          new int[] {a1x, a1y+1}, new int[] {a1x+1, a1y+1});

        // ensure this has changed before repaint
        if(shadowState == SHADOW_WALL && latestMotion.equals(shadowWall)) return false;
        else shadowWall = latestMotion;

        if(board.isLegalMove(shadowWall) == hardquorUserGame.ERROR_NONE)
          shadowState = SHADOW_WALL;
        else
          shadowState = SHADOW_WALL_ILLEGAL;

        return true;
      }

      int vertical_wall_row = -1;
      int vertical_wall_col = -1;

      for(int r = 0; r < skin.WALL_VERTICAL_Y.length; r++)
      { if(mouseY > skin.WALL_VERTICAL_Y[r] && mouseY < skin.WALL_VERTICAL_Y[r] + skin.WALL_VERTICAL_HEIGHT)
        { vertical_wall_row = r;
          break;
        }
      }

      for(int c = 0; c < skin.WALL_VERTICAL_X.length; c++)
      { if(mouseX > skin.WALL_VERTICAL_X[c] - skin.HOT_SPOT_WALL_PADDING
          && mouseX < skin.WALL_VERTICAL_X[c] + skin.WALL_VERTICAL_WIDTH + skin.HOT_SPOT_WALL_PADDING)
        { vertical_wall_col = c;
          break;
        }
      }

      if(vertical_wall_row != -1 && vertical_wall_col != -1)
      { int a1x = vertical_wall_col;
        int a1y = vertical_wall_row;
        // should ensure this has changed before repaint! ******************************************
        shadowWall = new WallMove(new int[] {a1x, a1y}, new int[] {a1x, a1y+1},
          new int[] {a1x+1, a1y}, new int[] {a1x+1, a1y+1});

        if(board.isLegalMove(shadowWall) == hardquorUserGame.ERROR_NONE)
          shadowState = SHADOW_WALL;
        else
          shadowState = SHADOW_WALL_ILLEGAL;

        // check to see if move REALLY changed
        return true;
      }

      // there was a shadow but now it's gone
      if(shadowState != SHADOW_NONE)
      { shadowState = SHADOW_NONE;
        return true;

      // no shadow before or after; no change
      } else
      { return false;
      }
    }

    /**
     * builds a move and makes it in response to a mouse event
     */
    public void mouseReleased(MouseEvent e)
    { int gx = e.getX();
      int gy = e.getY();

      int walk_row = -1;
      int walk_col = -1;

      for(int r = 0; r < skin.HOT_SPOT_ROWS[0].length; r++)
      { if(gy < skin.HOT_SPOT_ROWS[1][r] && gy > skin.HOT_SPOT_ROWS[0][r])
        { walk_row = r;
          break;
        }
      }

      for(int c = 0; c < skin.HOT_SPOT_COLS[0].length; c++)
      { if(gx < skin.HOT_SPOT_COLS[1][c] && gx > skin.HOT_SPOT_COLS[0][c])
        { walk_col = c;
          break;
        }
      }

      if(walk_row != -1 && walk_col != -1)
      { int[] playerLoc = board.getPlayerLocation(board.getTurn());
        gameClient.tryMove(playerLoc, new int[] {walk_col, walk_row});
        return;
      }

      int horizontal_wall_row = -1;
      int horizontal_wall_col = -1;

      for(int c = 0; c < skin.WALL_HORIZONTAL_X.length; c++)
      { if(gx > skin.WALL_HORIZONTAL_X[c] && gx < skin.WALL_HORIZONTAL_X[c] + skin.WALL_HORIZONTAL_WIDTH)
        { horizontal_wall_col = c;
          break;
        }
      }

      for(int r = 0; r < skin.WALL_HORIZONTAL_Y.length; r++)
      { if(gy > skin.WALL_HORIZONTAL_Y[r] - skin.HOT_SPOT_WALL_PADDING
          && gy < skin.WALL_HORIZONTAL_Y[r] + skin.WALL_HORIZONTAL_HEIGHT + skin.HOT_SPOT_WALL_PADDING)
        { horizontal_wall_row = r;
          break;
        }
      }

      if(horizontal_wall_row != -1 && horizontal_wall_col != -1)
      { int a1x = horizontal_wall_col;
        int a1y = horizontal_wall_row;
        gameClient.tryMove(new int[] {a1x, a1y}, new int[] {a1x+1, a1y},
          new int[] {a1x, a1y+1}, new int[] {a1x+1, a1y+1});
        return;
      }

      int vertical_wall_row = -1;
      int vertical_wall_col = -1;

      for(int r = 0; r < skin.WALL_VERTICAL_Y.length; r++)
      { if(gy > skin.WALL_VERTICAL_Y[r] && gy < skin.WALL_VERTICAL_Y[r] + skin.WALL_VERTICAL_HEIGHT)
        { vertical_wall_row = r;
          break;
        }
      }

      for(int c = 0; c < skin.WALL_VERTICAL_X.length; c++)
      { if(gx > skin.WALL_VERTICAL_X[c] - skin.HOT_SPOT_WALL_PADDING
          && gx < skin.WALL_VERTICAL_X[c] + skin.WALL_VERTICAL_WIDTH + skin.HOT_SPOT_WALL_PADDING)
        { vertical_wall_col = c;
          break;
        }
      }

      if(vertical_wall_row != -1 && vertical_wall_col != -1)
      { int a1x = vertical_wall_col;
        int a1y = vertical_wall_row;
        gameClient.tryMove(new int[] {a1x, a1y}, new int[] {a1x, a1y+1},
          new int[] {a1x+1, a1y}, new int[] {a1x+1, a1y+1});
        return;
      }
    }
  }


  class HardUICloseHandler extends WindowAdapter
  { public HardUICloseHandler()
    {
    }
    public void windowClosing(WindowEvent e)
    { gameClient.gameQuit();
    }
  }


}